package com.medusa.aps.business.modules.modeling.mp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.exception.BusinessErrorCode;
import com.medusa.aps.business.model.pagination.Page;
import com.medusa.aps.business.modules.modeling.model.input.WorkingProcedureAuxiliaryDTO;
import com.medusa.aps.business.modules.modeling.model.input.WorkingProcedureDTO;
import com.medusa.aps.business.modules.modeling.model.input.WorkingProcedureEquipmentDTO;
import com.medusa.aps.business.modules.modeling.model.input.WorkingProcedureParam;
import com.medusa.aps.business.modules.modeling.model.output.ProcedureVO;
import com.medusa.aps.business.modules.modeling.model.output.WorkingProcedureVO;
import com.medusa.aps.business.modules.modeling.mp.entity.TechnologicalProcess;
import com.medusa.aps.business.modules.modeling.mp.entity.WorkingProcedure;
import com.medusa.aps.business.modules.modeling.mp.entity.WorkingProcedureAuxiliary;
import com.medusa.aps.business.modules.modeling.mp.entity.WorkingProcedureEquipment;
import com.medusa.aps.business.modules.modeling.mp.mapper.WorkingProcedureMapper;
import com.medusa.aps.business.modules.modeling.mp.service.*;
import com.medusa.aps.business.util.ExcelUtils;
import com.medusa.aps.business.common.model.resp.SystemCode;
import com.medusa.aps.business.common.redis.annotation.Redisson;
import com.medusa.aps.business.global.model.exception.GlobalException;
import com.medusa.aps.business.global.model.exception.collector.DistinctValidator;
import com.medusa.aps.business.global.model.exception.collector.ErrorCollector;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 工序服务实现类
 *
 * @author yuxh
 * @since 2023-08-14 17:37:53
 */
@Slf4j
@Service("workingProcedureService")
public class WorkingProcedureServiceImpl extends ServiceImpl<WorkingProcedureMapper, WorkingProcedure> implements WorkingProcedureService {

    @Resource
    private WorkingProcedureEquipmentService workingProcedureEquipmentService;
    @Resource
    private WorkingProcedureAuxiliaryService workingProcedureAuxiliaryService;
    @Resource
    private TechnologicalProcessService technologicalProcessService;
    @Resource
    private ProcessRouteService processRouteService;

    /**
     * 分页查询工序列表
     *
     * @param page                  分页参数
     * @param workingProcedureParam 查询参数
     * @return 分页数据
     */
    @Override
    public Page<WorkingProcedureVO> selectWorkingProcedurePage(Page<WorkingProcedure> page, WorkingProcedureParam workingProcedureParam) {
        return baseMapper.selectWorkingProcedurePage(page, workingProcedureParam);
    }

    @Override
    public WorkingProcedureVO detail(Integer id) {
        return baseMapper.detail(id);
    }

    /**
     * 新增工序
     *
     * @param workingProcedures 参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.EQUIPMENT_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.AUXILIARY_DATA_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.WORKING_PROCEDURE_LOCK_KEY, waitTime = 1)
    public void saveWorkingProcedure(List<WorkingProcedureDTO> workingProcedures) {
        //校验工序数据
        validProcedure(workingProcedures);
        List<WorkingProcedure> procedures = workingProcedures.stream()
                .map(WorkingProcedureDTO::toWorkingProcedure)
                .collect(Collectors.toList());
        boolean flag = saveBatch(procedures);
        if (!flag) {
            throw new GlobalException(BusinessErrorCode.FAIL.getCode(), "工序添加失败");
        }
        List<WorkingProcedureAuxiliary> workingProcedureAuxiliaries = new ArrayList<>();
        //抽取工序编码与新增自动返回的工序id
        Map<String, Integer> idMap = procedures.stream().collect(Collectors.toMap(WorkingProcedure::getProcessCode, WorkingProcedure::getId));
        for (WorkingProcedureDTO workingProcedure : workingProcedures) {
            Integer processId = idMap.get(workingProcedure.getProcessCode());
            //处理工序设备数据
            //时间换取空间策略
            for (WorkingProcedureEquipmentDTO workingProcedureEquipmentDTO : workingProcedure.getWorkingProcedureEquipments()) {
                WorkingProcedureEquipment workingProcedureEquipment = workingProcedureEquipmentDTO.toWorkingProcedureEquipment(processId);
                //获取数据库返回的数据主键id
                workingProcedureEquipmentService.save(workingProcedureEquipment);
                //处理工序设备下的辅助资源
                List<WorkingProcedureAuxiliaryDTO> workingProcedureAuxiliaryDTOList = workingProcedureEquipmentDTO.getWorkingProcedureAuxiliary();
                if (CollUtil.isNotEmpty(workingProcedureAuxiliaryDTOList)) {
                    for (WorkingProcedureAuxiliaryDTO workingProcedureAuxiliaryDTO : workingProcedureAuxiliaryDTOList) {
                        WorkingProcedureAuxiliary workingProcedureAuxiliary = workingProcedureAuxiliaryDTO.toWorkingProcedureAuxiliary(processId, workingProcedureEquipment.getId());
                        workingProcedureAuxiliaries.add(workingProcedureAuxiliary);
                    }
                }
            }
        }
        if (CollUtil.isNotEmpty(workingProcedureAuxiliaries)) {
            workingProcedureAuxiliaryService.saveBatch(workingProcedureAuxiliaries);
        }
    }

    /**
     * 校验新增工序数据
     *
     * @param workingProcedures ${@link WorkingProcedureDTO}
     */
    private void validProcedure(List<WorkingProcedureDTO> workingProcedures) {
        //校验传入的多个工序编码是否重复
        ErrorCollector errors = DistinctValidator.valid(workingProcedures, WorkingProcedureDTO::getProcessCode, "工序编码");
        if (errors.hasError()) {
            throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), errors.toString());
        }
        validProcedureEquipmentAndAuxiliary(workingProcedures);
        Set<String> processCodes = workingProcedures.stream().map(WorkingProcedureDTO::getProcessCode).collect(Collectors.toSet());
        List<WorkingProcedure> procedureList = lambdaQuery()
                .select(WorkingProcedure::getProcessCode)
                .in(WorkingProcedure::getProcessCode, processCodes)
                .list();
        //校验传入的工序编码是否在数据库中已存在
        if (CollUtil.isNotEmpty(procedureList)) {
            Set<String> processCodeSet = procedureList.stream().map(WorkingProcedure::getProcessCode).collect(Collectors.toSet());
            Map<Long, StringBuilder> errorMap = new HashMap<>(16);
            for (int i = 0; i < workingProcedures.size(); i++) {
                if (processCodeSet.contains(workingProcedures.get(i).getProcessCode())) {
                    ExcelUtils.handlerErrorMessage(errorMap, i + 1L, StrUtil.format("{}:工序编码已存在", workingProcedures.get(i).getProcessCode()));
                }
            }
            ExcelUtils.validErrorMessageData(errorMap);
        }
    }

    /**
     * 校验工序下是否有相同设备与辅助资源
     *
     * @param workingProcedures 新增的工序
     */
    private void validProcedureEquipmentAndAuxiliary(List<WorkingProcedureDTO> workingProcedures) {
        for (WorkingProcedureDTO workingProcedure : workingProcedures) {
            List<WorkingProcedureEquipmentDTO> workingProcedureEquipments = workingProcedure.getWorkingProcedureEquipments();
            Set<String> equipmentCodes = workingProcedureEquipments.stream().map(WorkingProcedureEquipmentDTO::getEquipmentNumber).collect(Collectors.toSet());
            if (equipmentCodes.size() != workingProcedureEquipments.size()) {
                throw new GlobalException(SystemCode.PARAM_VALID_ERROR_CODE, "同一工序，不支持选择重复的设备");
            }
            workingProcedureEquipments.forEach(item -> {
                List<WorkingProcedureAuxiliaryDTO> workingProcedureAuxiliary = item.getWorkingProcedureAuxiliary();
                if (CollUtil.isNotEmpty(workingProcedureAuxiliary)) {
                    Set<String> auxiliaryCodes = workingProcedureAuxiliary.stream().map(WorkingProcedureAuxiliaryDTO::getAuxiliaryDataCoding).collect(Collectors.toSet());
                    if (auxiliaryCodes.size() != workingProcedureAuxiliary.size()) {
                        throw new GlobalException(SystemCode.PARAM_VALID_ERROR_CODE, "同一工序的同一设备，不支持选择重复的辅助资源");
                    }
                }
            });

        }

    }

    /**
     * 编辑工序
     *
     * @param workingProcedure 工序实体
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.EQUIPMENT_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.AUXILIARY_DATA_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.WORKING_PROCEDURE_LOCK_KEY, waitTime = 1)
    public void updateWorkingProcedure(WorkingProcedureDTO workingProcedure) {
        //校验编辑的工序数据
        validWorkingProcedure(workingProcedure);
        //先删除在新增
        workingProcedureAuxiliaryService.lambdaUpdate().eq(WorkingProcedureAuxiliary::getWorkingProcedureId, workingProcedure.getId()).remove();
        workingProcedureEquipmentService.lambdaUpdate().eq(WorkingProcedureEquipment::getWorkingProcedureId, workingProcedure.getId()).remove();
        //更新工序
        lambdaUpdate().eq(WorkingProcedure::getId, workingProcedure.getId())
                .set(WorkingProcedure::getProcessName, workingProcedure.getProcessName())
                .set(WorkingProcedure::getRemarks, workingProcedure.getRemarks())
                .update();
        // 删除在新增工序设备和辅助资料
        List<WorkingProcedureEquipmentDTO> workingProcedureEquipments = workingProcedure.getWorkingProcedureEquipments();
        List<WorkingProcedureEquipment> equipments = workingProcedureEquipments
                .stream()
                .map(item -> item.toWorkingProcedureEquipment(workingProcedure.getId()))
                .collect(Collectors.toList());
        workingProcedureEquipmentService.saveBatch(equipments);
        Map<String, Integer> procedureEquipmentIdMap = equipments.stream().collect(Collectors.toMap(WorkingProcedureEquipment::getEquipmentNumber, WorkingProcedureEquipment::getId));
        List<WorkingProcedureAuxiliary> auxiliaries = new ArrayList<>();
        workingProcedureEquipments.forEach(item -> {
            List<WorkingProcedureAuxiliaryDTO> workingProcedureAuxiliary = item.getWorkingProcedureAuxiliary();
            if (CollUtil.isNotEmpty(workingProcedureAuxiliary)) {
                workingProcedureAuxiliary.forEach(auxiliaryDTO -> {
                    WorkingProcedureAuxiliary auxiliary = auxiliaryDTO.toWorkingProcedureAuxiliary(workingProcedure.getId(), procedureEquipmentIdMap.get(item.getEquipmentNumber()));
                    auxiliaries.add(auxiliary);
                });
            }
        });
        if (CollUtil.isNotEmpty(auxiliaries)) {
            workingProcedureAuxiliaryService.saveBatch(auxiliaries);
        }
        //处理工序变化量
        handlerWorkingProcedureRecorded(workingProcedure);
    }

    /**
     * 处理工序变化量
     *
     * @param workingProcedure 修改之后的数据
     */
    private void handlerWorkingProcedureRecorded(WorkingProcedureDTO workingProcedure) {
        List<WorkingProcedureEquipmentDTO> workingProcedureEquipments = workingProcedure.getWorkingProcedureEquipments();
        //设备与辅助资源键值对
        Map<Long, Set<Integer>> equipmentAndAuxiliary = workingProcedureEquipments.stream().collect(Collectors.toMap(WorkingProcedureEquipmentDTO::getEquipmentId,
                item -> Optional.ofNullable(item.getWorkingProcedureAuxiliary()).orElse(List.of()).stream().map(WorkingProcedureAuxiliaryDTO::getAuxiliaryId).collect(Collectors.toSet())
        ));
        //处理使用这道工序的工艺路线
        processRouteService.editeProcessWorkingProcedure(workingProcedure.getId(), equipmentAndAuxiliary);

    }

    /**
     * 校验编辑的工序数据
     *
     * @param workingProcedure 工序信息
     */
    private void validWorkingProcedure(WorkingProcedureDTO workingProcedure) {
        ErrorCollector equipmentsError = DistinctValidator.valid(workingProcedure.getWorkingProcedureEquipments(), WorkingProcedureEquipmentDTO::getEquipmentNumber, "设备编码");
        if (equipmentsError.hasError()) {
            throw new GlobalException(equipmentsError.toString());
        }
        //工序设备
        List<WorkingProcedureEquipmentDTO> workingProcedureEquipments = workingProcedure.getWorkingProcedureEquipments();
        workingProcedureEquipments.forEach(item -> {
            //辅助资源
            List<WorkingProcedureAuxiliaryDTO> workingProcedureAuxiliary = item.getWorkingProcedureAuxiliary();
            ErrorCollector auxiliaryError = DistinctValidator.valid(workingProcedureAuxiliary, WorkingProcedureAuxiliaryDTO::getAuxiliaryDataCoding, "辅助资源编");
            if (auxiliaryError.hasError()) {
                throw new GlobalException(auxiliaryError.toString());
            }
        });
        compareWorkingProcedure(workingProcedure.getId(), workingProcedure.getProcessCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.WORKING_PROCEDURE_LOCK_KEY, waitTime = 1)
    public void removeWorkingProcedure(List<Integer> processIds) {
        Optional.ofNullable(
                lambdaQuery()
                        .select(WorkingProcedure::getProcessCode)
                        .in(WorkingProcedure::getId, processIds)
                        .list()
        ).orElseThrow(() -> {
            throw new GlobalException(SystemCode.DATA_NOT_EXIST_CODE, "删除的工序都不存在");
        });
        List<TechnologicalProcess> technologicalProcesses = technologicalProcessService.lambdaQuery()
                .select(TechnologicalProcess::getProcessCode)
                .in(TechnologicalProcess::getProcessId, processIds)
                .list();
        if (CollUtil.isNotEmpty(technologicalProcesses)) {
            Set<String> processCodes = technologicalProcesses.stream()
                    .map(TechnologicalProcess::getProcessCode)
                    .collect(Collectors.toSet());
            throw new GlobalException(BusinessErrorCode.DATA_IS_USED.getCode(), StrUtil.format("工序编码：【{}】已被工艺路线使用！无法删除！", StrUtil.join(StrUtil.COMMA, processCodes)));
        }
        removeBatchByIds(processIds);
        workingProcedureAuxiliaryService.lambdaUpdate().in(WorkingProcedureAuxiliary::getWorkingProcedureId, processIds).remove();
        workingProcedureEquipmentService.lambdaUpdate().in(WorkingProcedureEquipment::getWorkingProcedureId, processIds).remove();
    }

    /**
     * 传入的工序数据与数据库原始数据进行比较
     *
     * @param workingProcedureId 工序id
     * @param processCode        工序编码
     */
    private void compareWorkingProcedure(Integer workingProcedureId, String processCode) {
        WorkingProcedureVO detail = baseMapper.detail(workingProcedureId);
        if (BeanUtil.isEmpty(detail)) {
            throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), "当前工序不存在");
        }
        if (!detail.getProcessCode().equals(processCode)) {
            throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), "工序编码不支持更改");
        }
    }

    @Override
    public Boolean existsProcessRoute(Integer id) {
        return technologicalProcessService.lambdaQuery()
                .eq(TechnologicalProcess::getProcessId, id)
                .exists();
    }

    @Override
    public List<ProcedureVO> selectWorkingProcedureBatch(Set<Integer> workingProcedureIds) {
        return baseMapper.selectWorkingProcedureBatch(workingProcedureIds);
    }
}

